#include "icmp.h"


#define ICMP_OK 0
#define ICMP_ERR -1

#define ICMP_HEADER_LEN 4
#define ICMP_PARAMETERS_LEN 4
#define ICMP_CHECKSUM_LEN 2
#define ICMP_CHECKSUM_POS 2

#define ICMP_PROTOCOL_NUMBER (0x01)

#define ICMP_ECHO_REQ_TYPE (0x08)
#define ICMP_ECHO_REP_TYPE (0x00)
#define ICMP_ECHO_CODE (0x00)

#define getEchoDataSize(size) ((size)-ICMP_HEADER_LEN-ICMP_PARAMETERS_LEN)
#define getEchoParametersPointer(data) ((data)+ICMP_HEADER_LEN)
#define getEchoData(data) ((data)+ICMP_HEADER_LEN+ICMP_PARAMETERS_LEN)

typedef enum {ICMP_ECHO_REP=0, ICMP_ECHO_REQ=8, ICMP_UNDEF=-1} ICMP_TYPE;

short icmp_inicialized=0;

CALLBACK_MEM callbackArray=NULL;

static BYTE ICMP_CHECKSUM_BASE_VALUE[]={0x00, 0x00};
static BYTE ICMP_CORRECT_CHECKSUM[]={0x00, 0x00};

int icmpNotify(BYTE *dir_ip_origen, unsigned tamano, BYTE *datos, op_ip *opciones);
BYTE *createICMPDatagram(BYTE type, BYTE code, BYTE *parameters, BYTE *data, unsigned int size, unsigned int *totalSize);
short isCorrectChecksum(BYTE *data, int size);
ICMP_TYPE getICMPType(BYTE *data);
int createEchoParametersICMP(unsigned int id, unsigned int seq, BYTE *parameters);
int getEchoParametersICMP(BYTE *parameters, unsigned int *id, unsigned int *seq);
int processEchoRepICMP(BYTE *dir_ip_origen, BYTE *data, unsigned int size, op_ip *opcions);
int processEchoReqICMP(BYTE *dir_ip_origen, BYTE *data, unsigned int size, op_ip *opcions);
int registerEchoCallbackICMP(unsigned int id, pf_notificacion_echo callback);
pf_notificacion_echo getEchoCallbackICMP(unsigned int id);


int icmpNotify(BYTE *dir_ip_origen, unsigned tamano, BYTE *datos, op_ip *opciones)
{
	ICMP_TYPE type;
	
	//printf("ICMP: Recibido mensaje\n");
	if(dir_ip_origen==NULL || (tamano>0 && datos==NULL) || tamano<ICMP_HEADER_LEN){
		return -1;
	}
	
	//printf("ICMP: Comprobando checksum\n");
	if(!isCorrectChecksum(datos, tamano)){
		//printf("ICMP: Checksum INvalido\n");
		return -1;
	}
	
	//printf("ICMP: Checksum valido\n");
	type=getICMPType(datos);
	switch(type)
	{
		case ICMP_ECHO_REQ:
			processEchoReqICMP(dir_ip_origen, datos, tamano, opciones);
			//printf("ICMP: Recibido ECHO REQUEST\n");
			return -1;
		
		case ICMP_ECHO_REP:
			//printf("ICMP: Recibido ECHO REPLY\n");
			processEchoRepICMP(dir_ip_origen, datos, tamano, opciones);
			return -1;
		
		default:
			//printf("ICMP: Tipo desconocido\n");
			return -1;
	}
	
	return -1;
}

int icmp_inicializa()
{
	if(icmp_inicialized){
		return ICMP_OK;
	}
	
	if(ip_inicializa()!=0){
		return ICMP_ERR;
	}
	
	if(ip_registra_protocolo(ICMP_PROTOCOL_NUMBER, icmpNotify)!=0){
		return ICMP_ERR;
	}
	
	callbackArray=createCallbackMem();
	if(callbackArray==NULL){
		return ICMP_ERR;
	}
	
	icmp_inicialized=1;
	
	return ICMP_OK;
}

int icmp_finaliza()
{
	freeCallbackMem(callbackArray);
	
	icmp_inicialized=0;
	
	return ICMP_OK;
}

int icmp_envia_echo(BYTE *dir_ip_destino, unsigned int id,
  unsigned int seq, pf_notificacion_echo callback,
  unsigned int tamano, BYTE *datos, op_ip *opciones)
{
	BYTE parameters[ICMP_PARAMETERS_LEN];
	
	if(dir_ip_destino==NULL || (tamano>0 && datos==NULL)){
		return ICMP_ERR;
	}
	
	if(registerEchoCallbackICMP(id, callback)!=ICMP_OK){
		return ICMP_ERR;
	}
	
	if(createEchoParametersICMP(id, seq, parameters)!=ICMP_OK){
		return ICMP_ERR;
	}
	
	if(icmp_envia_datagrama(dir_ip_destino, ICMP_ECHO_REQ_TYPE, ICMP_ECHO_CODE, parameters, tamano, datos, opciones)!=ICMP_OK){
		return ICMP_ERR;
	}
	
	return ICMP_OK; 
}


int icmp_envia_datagrama(BYTE *dir_ip_destino, BYTE tipo, BYTE codigo,
  BYTE *parametros, unsigned int tamano, BYTE *datos, op_ip *opciones)
{
	BYTE *icmp_datagram;	
	unsigned int tamanoTotal;
	
	if(dir_ip_destino==NULL || (tamano>0 && datos==NULL)){
		return ICMP_ERR;
	}
	
	icmp_datagram=createICMPDatagram(tipo, codigo, parametros, datos, tamano, &tamanoTotal);
	if(icmp_datagram==NULL){
		return ICMP_ERR;
	}
	
	if(ip_envia_datagrama(ICMP_PROTOCOL_NUMBER, dir_ip_destino, tamanoTotal, icmp_datagram, opciones)!=0){
		free(icmp_datagram);
		return ICMP_ERR;
	}
	
	return ICMP_OK;
}

BYTE *createICMPDatagram(BYTE type, BYTE code, BYTE *parameters, BYTE *data, unsigned int size, unsigned int *totalSize)
{
	BYTE *datagram=NULL;
	int offset=0;
	
	if(parameters!=NULL){
		*totalSize=ICMP_HEADER_LEN+ICMP_PARAMETERS_LEN+size;
	}else{
		*totalSize=ICMP_HEADER_LEN+size;
	}
	
	datagram=(BYTE *)calloc(*totalSize, sizeof(BYTE));
	if(datagram==NULL){
		return NULL;
	}
	
	datagram[offset++]=type;
	datagram[offset++]=code;
	memcpy(datagram+offset, ICMP_CHECKSUM_BASE_VALUE, ICMP_CHECKSUM_LEN);
	offset+=ICMP_CHECKSUM_LEN;
		
	if(parameters!=NULL){
		memcpy(datagram+offset, parameters, ICMP_PARAMETERS_LEN);
		offset+=ICMP_PARAMETERS_LEN;
	}
	
	if(data!=NULL){
		memcpy(datagram+offset, data, size);
	}
	
	calcula_checksum_ip(*totalSize, datagram, datagram+ICMP_CHECKSUM_POS);

	return datagram;
}

short isCorrectChecksum(BYTE *data, int size)
{
	BYTE checksum[ICMP_CHECKSUM_LEN];
	
	calcula_checksum_ip(size, data, checksum);
	
	if(memcmp(checksum, ICMP_CORRECT_CHECKSUM, ICMP_CHECKSUM_LEN)!=0){
		return 0;
	}
	
	return 1;
}

ICMP_TYPE getICMPType(BYTE *data)
{
	if(data==NULL){
		return ICMP_UNDEF;
	}
	
	if(data[0]==ICMP_ECHO_REP_TYPE && data[1]==ICMP_ECHO_CODE){
		return ICMP_ECHO_REP;
	}
	else if(data[0]==ICMP_ECHO_REQ && data[1]==ICMP_ECHO_CODE){
		return ICMP_ECHO_REQ;
	}
	
	return ICMP_UNDEF;
}


int createEchoParametersICMP(unsigned int id, unsigned int seq, BYTE *parameters)
{
	uint16_t *uintParameters;
	
	if(parameters==NULL){
		return ICMP_ERR;
	}
	
	uintParameters=(uint16_t *) parameters;
	
	uintParameters[0]=htons(id);
	uintParameters[1]=htons(seq);
	
	return ICMP_OK;
}

int getEchoParametersICMP(BYTE *parameters, unsigned int *id, unsigned int *seq)
{
	uint16_t *uintParameters;
	
	uintParameters=(uint16_t *) parameters;
	
	*id=ntohs(uintParameters[0]);
	*seq=ntohs(uintParameters[1]);
	
	return ICMP_OK;
}

int processEchoRepICMP(BYTE *dir_ip_origen, BYTE *data, unsigned int size, op_ip *opcions)
{
	unsigned int id, seq;
	pf_notificacion_echo callbackFun;
	
	if(getEchoParametersICMP(getEchoParametersPointer(data), &id, &seq)!=ICMP_OK){
		//printf("PROCESS ECHO REPLY: Parametros incorrectos\n");
		return ICMP_ERR;
	}
	
	callbackFun=getEchoCallbackICMP(id);
	if(callbackFun==NULL){
		//printf("PROCESS ECHO REPLY: No se encuentra la funcion de notificacion\n");
		return ICMP_ERR;
	}
	
	return callbackFun(dir_ip_origen, seq, getEchoDataSize(size), getEchoParametersPointer(data), opcions);
}

int processEchoReqICMP(BYTE *dir_ip_origen, BYTE *data, unsigned int size, op_ip *opcions)
{
	if(icmp_envia_datagrama(dir_ip_origen, ICMP_ECHO_REP_TYPE, ICMP_ECHO_CODE, getEchoParametersPointer(data),
							getEchoDataSize(size), getEchoData(data), opcions)!=ICMP_OK){
		return ICMP_ERR;
	}
	
	return ICMP_OK;
}

int registerEchoCallbackICMP(unsigned int id, pf_notificacion_echo callback)
{
	if(registerCallback(callbackArray, id, callback)==CALLBACK_MEM_ERR){
		return ICMP_ERR;
	}
	
	return ICMP_OK;

}

pf_notificacion_echo getEchoCallbackICMP(unsigned int id)
{
	return getCallback(callbackArray, id);
}

